#pragma once

TODO("Separate the addition of constants from game files to DLL or console script")

class CConstants;
class CEnum;
class CEnums;
class CEnumerator;

// bit of weirdness with storage here

template<class T>
class CConstant : public CIdentifier
{
	friend class CConstants;

	T							m_Val;

protected:
	CConstant(const char *szName, T nVal, const CEnum * pEnum = nullptr) : CIdentifier(szName),
		m_Val(nVal)
	{
	}

public:
	inline T									GetValue() const				{return m_Val;}

	template<class U>
	const CConstant<U>				*			Convert() const					{return (const CConstant<U>*)this;}
	
	virtual const CEnumerator				*	Enumerate()	const				{return nullptr;}
	virtual bool								IsEnumerator() const			{return false;}
	virtual bool								IsScriptDefined() const			{return false;}
};

template<class T>
class CScriptConstant : public CConstant<T>
{
	friend class CConstants;

	PType			m_BasicType;

protected:
	CScriptConstant(const char *szName, T val, PType type)		:	CConstant<T>(szName, val),
		m_BasicType(type)
	{}

public:
	PType						GetType() const						{return m_BasicType;}
	bool						IsScriptDefined() const override	{return true;}
};

class CEnumerator : public CConstant<int>
{
	friend class CEnum;
	friend class CConstants;

	const CEnum				*	m_pEnum;

	CEnumerator(const char *szName, int value, const CEnum * pEnum) : CConstant(szName, value),
		m_pEnum(pEnum)
	{
	}

	const CEnumerator		*	Enumerate() const override		{return this;}
	bool						IsEnumerator() const override	{return true;}

public:
	const CEnum				*	GetEnum() const					{return m_pEnum;}
};

class CEnum : public CIdentifier
{
	friend class CEnums;
	friend class CConstants;

	CLListEx<const CEnumerator*>	m_Enumerators;
	CList<const CEnumerator>		m_EnumeratorList;
	int								m_nNumEnumerators;

	void Add(const CEnumerator * pEnumerator)
	{
		m_Enumerators.Add(pEnumerator);
		m_EnumeratorList.Add(pEnumerator->GetHash(), pEnumerator);
	}

public:
	CEnum(const char *szName) : CIdentifier(szName),
		m_nNumEnumerators(0)
	{}

	const CEnumerator * Add(const char *szName)
	{
		auto pEnumerator = new CEnumerator(szName, m_nNumEnumerators++, this);
		Add(pEnumerator);
		return pEnumerator;
	}

	const CEnumerator * GetEnumerator(const char *szName) const
	{
		return m_EnumeratorList.Get(GenerateHash(szName));
	}

template<typename T>
	const CEnumerator * GetEnumeratorWithValue(T val) const
	{
		for(auto pLink = m_Enumerators.Begin(); pLink != m_Enumerators.Bad(); ++pLink)
		{
			if(pLink->GetValue<T>() == val) return *pLink;
		}
		return nullptr;
	}
};

class CConstants
{
	static CList<const CConstant<int>>		s_pConstantList;
	static CList<const CConstant<int>>		s_pConstantFileList;
	static CList<const CConstant<int>>		s_pConstantModelList;

	static CLListEx<const CIdentifier*>		s_pScriptIdentifiers;

	static int								s_nNumConstants;
	static int								s_nNumFileConstants;
	static int								s_nNumFileEnumerators;
	static int								s_nNumModelConstants;
	static int								s_nNumScriptConstants;
	static int								s_nNumScriptEnumerators;
	static const CConstant<int>			**	s_pModelsList;
	
	static bool ParseIdeLine(char *);

template<class T>
	static const CConstant<T> * Add(const char *szName, T nVal)
	{
		auto pConstant = new CConstant<T>(szName, nVal);
		if(pConstant)
		{
			s_pConstantList.Add(pConstant->GetHash(), pConstant);
			++s_nNumConstants;
		}
		return pConstant;
	}
	static void AddFromFile(const char *szName, int nVal)
	{
		if(auto pConstant = Add<int>(szName, nVal))
		{
			s_pConstantFileList.Add(pConstant->GetHash(), pConstant);
			++s_nNumFileConstants;
		}
	}
	static void AddForModel(const char *szName, int nVal)
	{
		if(auto pConstant = Add<int>(szName, nVal))
		{
			s_pConstantModelList.Add(pConstant->GetHash(), pConstant);
			++s_nNumModelConstants;
		}
	}
	static void AddFromFile(const CEnumerator * pEnumerator)
	{
		if(pEnumerator)
		{
			s_pConstantList.Add(pEnumerator->GetHash(), pEnumerator);
			++s_nNumConstants;
			++s_nNumFileEnumerators;
		}
	}

public:
	static bool LoadFromFile(const char *);
	static bool LoadIdeFile(const char *, bool = 0);
	static bool LoadDatFile(const char *);

	static void Add(const CEnumerator * pEnumerator)
	{
		if(!pEnumerator) return;
		s_pConstantList.Add(pEnumerator->GetHash(), pEnumerator);
		s_pScriptIdentifiers.Add(pEnumerator);
		++s_nNumConstants;
		++s_nNumScriptEnumerators;
	}

template<class T>
	static const CScriptConstant<T> * AddForScript(const char * szName, T val, PType type)
	{
		auto pConstant = new CScriptConstant<T>(szName, val, type);
		s_pConstantList.Add(pConstant->GetHash(), pConstant->Convert<int>());
		s_pScriptIdentifiers.Add(pConstant);
		++s_nNumConstants;
		++s_nNumScriptConstants;
		return pConstant;
	}

	static const CConstant<int> * Get(const char *szName)
	{
		DWORD hash = GenerateHash(szName);
		auto pConstant = s_pConstantList.Get(hash);
		while(pConstant && !pConstant->IsNamed(szName, hash))
			pConstant = s_pConstantList.Next();
		return pConstant;
	}
	static const CConstant<int> * GetFromFile(const char *szName)
	{
		DWORD hash = GenerateHash(szName);
		auto pConstant = s_pConstantFileList.Get(hash);
		while(pConstant && !pConstant->IsNamed(szName, hash))
			pConstant = s_pConstantFileList.Next();
		return pConstant;
	}
	static const CConstant<int> * GetModel(const char *szName)
	{
		DWORD hash = GenerateHash(szName);
		auto pConstant = s_pConstantModelList.Get(hash);
		while(pConstant && !pConstant->IsNamed(szName, hash))
			pConstant = s_pConstantModelList.Next();
		return pConstant;
	}
	static void Reset()
	{
		s_pConstantList.Clear();
		s_pScriptIdentifiers.Clear();
		s_nNumConstants = 0;
	}
};

class CEnums
{
	static int							s_nNumEnums;

public:
	static CEnum * Add(const char *szName)
	{
		auto pEnum = new CEnum(szName);
		++s_nNumEnums;
		return pEnum;
	}
};